﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace MirrorFolder
{
    public partial class MirrorElement : UserControl
    {
        #region *** переменные ********************************************************************

        public static int NextUIN = 1;

        int UIN = 0;
        public delegate bool StartSyncDelegate(object sender);
        public event EventHandler Delete;
        public event EventHandler ChangeData;
        public event StartSyncDelegate StartSync;
        public event EventHandler EndSync;
        MElementSettings Sett = new MElementSettings();
        bool IsActive = false;
        bool LastSyncIsGood = false;
        public DateTime LastSync = DateTime.MinValue;
        public DateTime LastChange = DateTime.MinValue;

        DirSync sync = new DirSync();
        DirSync.Snapshot lastSnapshot = null;

        #endregion *** переменные *****************************************************************

        public MirrorElement()
        {
            InitializeComponent();
            UIN = NextUIN;
            NextUIN++;
            RefillInfo();
            ChangeIsActive(IsActive);
        }

        public MirrorElement(BinaryReader Br)
        {
            InitializeComponent();
            LoadElement(Br);
            RefillInfo();
            ChangeIsActive(IsActive);
        }

        #region *** Load/Save *********************************************************************

        void LoadElement(BinaryReader Br)
        {
            int ver = Br.ReadByte();
            switch (ver)
            {
                case 0: LoadElement0(Br); break;
                case 1: LoadElement1(Br); break;
                default: throw new Exception("Неподдерживаемая версия элемента");
            }

        }

        void LoadElement0(BinaryReader Br)
        {
            UIN = Br.ReadInt32();
            lblDirSrs.Text = Br.ReadString();
            lblDirDest.Text = Br.ReadString();
            IsActive = Br.ReadBoolean();
            LastSync = new DateTime(Br.ReadInt64());
            LastChange = new DateTime(Br.ReadInt64());
            LastSyncIsGood = Br.ReadBoolean();
            bool dualSync = Br.ReadBoolean();
            lastSnapshot = null;
            bool lastSnapshotExist = Br.ReadBoolean();
            if (lastSnapshotExist) lastSnapshot = new DirSync.Snapshot(Br);
            Sett.Schd.Load(Br);
            Sett.SourceDir = lblDirSrs.Text;
            Sett.DestDir = lblDirDest.Text;
            Sett.DualSync = dualSync;
            tBtnRecover.Visible = Sett.Mode == ElementMode.Archive;
        }

        void LoadElement1(BinaryReader Br)
        {
            UIN = Br.ReadInt32();
            IsActive = Br.ReadBoolean();
            LastSync = new DateTime(Br.ReadInt64());
            LastChange = new DateTime(Br.ReadInt64());
            LastSyncIsGood = Br.ReadBoolean();
            lastSnapshot = null;
            bool lastSnapshotExist = Br.ReadBoolean();
            if (lastSnapshotExist) lastSnapshot = new DirSync.Snapshot(Br);
            Sett.Load(Br);
            lblDirSrs.Text = Sett.SourceDir;
            lblDirDest.Text = Sett.DestDir;
            tBtnRecover.Visible = Sett.Mode == ElementMode.Archive;
        }

        public void SaveElement(BinaryWriter Bw)
        {
            Bw.Write((byte)1); // задел на версионность
            Bw.Write(UIN);
            Bw.Write(IsActive);
            Bw.Write(LastSync.Ticks);
            Bw.Write(LastChange.Ticks);
            Bw.Write(LastSyncIsGood);
            Bw.Write(lastSnapshot != null);
            if (lastSnapshot != null) lastSnapshot.Save(Bw);
            Sett.Save(Bw);
        }

        #endregion *** Load/Save ******************************************************************

        void FillInfo()
        {

        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            if (Sett.SourceDir.Trim() == "" || Sett.DestDir.Trim() == "")
            {
                MessageBox.Show("Не выбраны каталоги");
                return;
            }
            ChangeIsActive(!IsActive);
            if (ChangeData != null) ChangeData(this, null);
        }

        void ChangeIsActive(bool NewVal)
        {
            IsActive = NewVal;
            btnStart.Image = IsActive ? global::MirrorFolder.Properties.Resources.run1 : global::MirrorFolder.Properties.Resources.run2;
            //tmrMain.Enabled = IsActive;
        }

        private void btnDelete_Click(object sender, EventArgs e)
        {
            if (Delete != null) Delete(this, null);
        }


        private void tmrMain_Tick(object sender, EventArgs e)
        {
            tmrMain.Stop();

            // если идет синхронизация
            if (sync.State != DirSync.ExecuteState.None)
            {
                if (sync.State == DirSync.ExecuteState.Sync || sync.State == DirSync.ExecuteState.CancelSync)
                {
                    progrSync.Value = sync.Percent;
                }
                else
                {
                    if (EndSync != null) EndSync(this, new EventArgs());
                    pnlSync.Visible = false;
                    if (Sett.Schd.Mode == ScheduleMode.None)
                    {
                        if (IsActive) ChangeIsActive(!IsActive);
                    }
                    else
                    {
                        if (sync.ErrorText != "") Sett.Schd.NextSync = DateTime.Now.AddMinutes(5);
                        else Sett.Schd.SetNextSyncDate();
                    }

                    LastSync = DateTime.Now;
                    LastSyncIsGood = sync.State == DirSync.ExecuteState.SyncEnd_Good;
                    if (sync.ActionList.Count > 0 && sync.ActionList.Exists(action => action.IsExecuted)) LastChange = DateTime.Now;
                    if (LastSyncIsGood) lastSnapshot = sync.NewSnapshot;
                    RefillInfo();

                    // запись лога
                    AddLogMain(sync.ActionList, sync.LastArchDate, sync.CreatedFullCopy, sync.State, sync.ErrorText);
                    switch (sync.State)
                    {
                        case DirSync.ExecuteState.SyncEnd_Err: AddLog("Ошибка синхронизации: " + sync.ErrorText, null); ttSyncInfo.SetToolTip(lblLastSync, "Ошибка синхронизации: " + sync.ErrorText); break;
                        case DirSync.ExecuteState.SyncEnd_Cancel: AddLog("Отмена синхронизации пользователем", null); ttSyncInfo.SetToolTip(lblLastSync, "Отмена синхронизации пользователем"); break;
                        case DirSync.ExecuteState.SyncEnd_Good: AddLogEndSync_Good(sync.ActionList); break;
                        default: AddLog("Не удалось определить результат синхронизации " + sync.State.ToString(), null); ttSyncInfo.SetToolTip(lblLastSync, "Не удалось определить результат синхронизации " + sync.State.ToString()); break;
                    }

                    sync.State = DirSync.ExecuteState.None;
                    if (ChangeData != null) ChangeData(this, null);
                }
                tmrMain.Start();
                return;
            }

            // если элемент синхронизации запущен
            if (SyncNow || (IsActive && (Sett.Schd.Mode == ScheduleMode.None || Sett.Schd.NextSync <= DateTime.Now)))
            {
                try
                {
                    // проверка возможности запуска
                    if (StartSync != null && !StartSync(this))
                    {
                        if (SyncNow)
                        {
                            MessageBox.Show("Невозможно запустить, так как выполняется другой элемент");
                            SyncNow = false;
                        }
                        else
                            Sett.Schd.NextSync = DateTime.Now.AddSeconds(10);
                        tmrMain.Start();
                        return;
                    }

                    SyncNow = false;

                    if (!Directory.Exists(Sett.SourceDir.Trim()) || !Directory.Exists(Sett.DestDir.Trim()))
                    {
                        sync.State = DirSync.ExecuteState.SyncEnd_Err;
                        sync.ErrorText = "Ошибка старта синхронизации: Один из каталогов недоступен";
                    }
                    else
                    {
                        pnlSync.Visible = true;
                        pnlSync.BringToFront();
                        btnCancelSync.Enabled = true;
                        progrSync.Maximum = 100;
                        progrSync.Value = 0;

                        if (Sett.Mode == ElementMode.Mirror)
                        {
                            if (Sett.DualSync)
                            {
                                AddLog("Старт двусторонней синхронизации" + (lastSnapshot == null ? " (ВПЕРВЫЕ)" : ""), null);
                                sync.StartSyncDirectories(Sett.SourceDir.Trim(), Sett.DestDir.Trim(), true, lastSnapshot, true);
                            }
                            else
                            {
                                AddLog("Старт односторонней синхронизации", null);
                                sync.StartSyncDirectories(Sett.SourceDir.Trim(), Sett.DestDir.Trim(), false, null, true);
                            }
                        }
                        else
                        {
                            AddLog("Старт архивации", null);
                            sync.StartArchiveDirectory(Sett.SourceDir.Trim(), Sett.DestDir.Trim(), Sett.ArchMode, Sett.LineSize, Sett.LinesCount);
                        }
                    }
                }
                catch (Exception ex)
                {
                    AddLog("Ошибка старта синхронизации каталогов", ex);
                }

            }

            tmrMain.Start();
        }

        #region *** Работа с логами ************************************************************************

        string GetFNameLog()
        {
            if (Sett.SourceDir.Trim() == "") return UIN.ToString();
            string[] sl = Sett.SourceDir.Split('\\');
            string nm = sl[sl.Length - 1].Trim();
            if (sl[sl.Length - 1].Trim() == "" && sl.Length > 1) nm = sl[sl.Length - 2].Trim();
            return string.Format("{0} ({1})", nm, UIN.ToString());
        }

        private void btnLog_Click(object sender, EventArgs e)
        {
            string fPath = string.Format("{0}\\{1} Main.log", Gl.DPathLogs, GetFNameLog());
            if (!File.Exists(fPath))
            {
                MessageBox.Show("Значимых действий еще небыло");
                return;
            }
            System.Diagnostics.Process.Start(fPath);
        }

        void AddLog(string Mess)
        {
            AddLog(Mess, null);
        }

        void AddLog(string Mess, Exception ex)
        {
            try
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine(string.Format("[{0}]", DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss,fff")));
                sb.AppendLine(Mess);
                if (ex != null) AddLogEx(ex, sb, 0);
                sb.AppendLine("-------------------------------");
                sb.AppendLine("");
                File.AppendAllText(string.Format("{0}\\{1}.log", Gl.DPathLogs, GetFNameLog()), sb.ToString());
            }
            catch { }
        }

        void AddLogEx(Exception ex, StringBuilder sb, int level)
        {
            sb.AppendLine(level == 0 ? "Исключение:" : "Дочернее исключение:");
            sb.AppendLine("\tТекст: " + ex.Message);
            sb.AppendLine("\tТрассировка: " + ex.StackTrace);
            if (ex.InnerException != null) AddLogEx(ex.InnerException, sb, level + 1);
        }

        void AddLogEndSync_Good(List<DirSync.SyncAction> ActionList)
        {
            if (ActionList.Count == 0) { AddLog("Синхронизация выполнена успешно. Изменений нет"); ttSyncInfo.SetToolTip(lblLastSync, "Синхронизация выполнена успешно. Изменений нет"); return; }
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Синхронизация выполнена успешно. Изменения:");
            foreach (DirSync.SyncAction action in ActionList)
                sb.AppendLine("\t- " + action.ToString(Sett.Mode == ElementMode.Mirror));
            AddLog(sb.ToString());
            ttSyncInfo.SetToolTip(lblLastSync, sb.ToString());
            ttSyncInfo.SetToolTip(lblLastChange, sb.ToString());
        }

        void AddLogMain(List<DirSync.SyncAction> ActionList, DateTime LastArchDate, bool CreatedFullCopy, DirSync.ExecuteState ResultState, string ErrStr)
        {
            bool bChange = ActionList.Count != 0 && ActionList.Exists(a => a.IsExecuted);
            if (Sett.Mode == ElementMode.Mirror && !bChange && ResultState != DirSync.ExecuteState.SyncEnd_Err) return;
            if (ResultState == DirSync.ExecuteState.SyncEnd_Err && ErrStr == "Ошибка старта синхронизации: Один из каталогов недоступен") return;

            StringBuilder sb = new StringBuilder();
            sb.AppendLine(string.Format("{0}[{1}]", ResultState == DirSync.ExecuteState.SyncEnd_Err ? "*** ОШИБКА *** " : "", DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss,fff")));

            #region *** наименование действия ************
            if (Sett.Mode == ElementMode.Mirror)
            {
                switch (ResultState)
                {
                    case DirSync.ExecuteState.SyncEnd_Good: sb.AppendLine("Успешная синхронизация"); break;
                    case DirSync.ExecuteState.SyncEnd_Cancel: sb.AppendLine("Прерванная синхронизация"); break;
                    case DirSync.ExecuteState.SyncEnd_Err: sb.AppendLine(bChange ? "Синхронизация с ошибкой" : "Ошибка синхронизации"); break;
                }
            }
            else
            {
                switch (ResultState)
                {
                    case DirSync.ExecuteState.SyncEnd_Good: sb.AppendLine("Успешная архивация"); break;
                    case DirSync.ExecuteState.SyncEnd_Cancel: sb.AppendLine("Прерванная архивация"); break;
                    case DirSync.ExecuteState.SyncEnd_Err: sb.AppendLine("Ошибка архивации"); break;
                }
            }
            #endregion *** наименование действия ************

            #region *** успешная операция ************

            if (ResultState == DirSync.ExecuteState.SyncEnd_Good)
            {
                if (Sett.Mode == ElementMode.Archive)
                {
                    if (LastArchDate != DateTime.MinValue) sb.Append(string.Format("Список изменений от {0}:", LastArchDate));
                    if (CreatedFullCopy) sb.AppendLine("НОВАЯ ПОЛНАЯ КОПИЯ");
                }

                if (bChange)
                {
                    List<DirSync.SyncAction> executedList = ActionList.FindAll(a => a.IsExecuted);
                    foreach (DirSync.SyncAction action in executedList) sb.AppendLine("\t- " + action.ToString(Sett.Mode == ElementMode.Mirror));
                }
                else
                {
                    if (!CreatedFullCopy) sb.Append(" Нет\r\n");
                }
            }

            #endregion *** успешная операция ************

            #region *** операция c ошибкой или отмененная ************

            if (ResultState != DirSync.ExecuteState.SyncEnd_Good)
            {
                if (Sett.Mode == ElementMode.Mirror)
                {
                    sb.Append("Выполненные действия:");
                    if (bChange)
                    {
                        List<DirSync.SyncAction> executedList = ActionList.FindAll(a => a.IsExecuted);
                        foreach (DirSync.SyncAction action in executedList) sb.AppendLine("\t- " + action.ToString(Sett.Mode == ElementMode.Mirror));
                    }
                    else
                    {
                        sb.Append(" Нет\r\n");
                    }
                    if (ResultState == DirSync.ExecuteState.SyncEnd_Err) sb.AppendLine("ОШИБКА: " + ErrStr);
                    if (ResultState != DirSync.ExecuteState.SyncEnd_Good) sb.Append("Невыполненные действия:");
                    if (bChange)
                    {
                        List<DirSync.SyncAction> notExecutedList = ActionList.FindAll(a => !a.IsExecuted);
                        foreach (DirSync.SyncAction action in notExecutedList) sb.AppendLine("\t- " + action.ToString(Sett.Mode == ElementMode.Mirror));
                    }
                    else
                    {
                        sb.Append(" Нет\r\n");
                    }
                }
            }
            #endregion *** операция c ошибкой или отмененная ************

            sb.AppendLine("-------------------------------");
            sb.AppendLine("");

            File.AppendAllText(string.Format("{0}\\{1} Main.log", Gl.DPathLogs, GetFNameLog()), sb.ToString());
        }

        #endregion *** Работа с логами *******************************************************************

        private void btnSchedule_Click(object sender, EventArgs e)
        {
            FormMElementSettings frm = new FormMElementSettings();
            if (frm.ShowDialog(Sett.Clone()))
            {
                Sett = frm.Sett;
                if (Sett.Schd.Mode != ScheduleMode.None) Sett.Schd.SetNextSyncDate();
                RefillInfo();
                lblDirSrs.Text = Sett.SourceDir;
                lblDirDest.Text = Sett.DestDir;
                RefillInfo();
                tBtnRecover.Visible = Sett.Mode == ElementMode.Archive;
                if (ChangeData != null) ChangeData(this, null);
            }

        }

        void RefillInfo()
        {
            lblLastSync.Text = LastSync == DateTime.MinValue ? "Нет" : LastSync.ToString("dd.MM.yy HH:mm:ss");
            lblLastChange.Text = LastChange == DateTime.MinValue ? "Нет" : LastChange.ToString("dd.MM.yy HH:mm:ss");
            lblNextSync.Text = Sett.Schd.NextSync == DateTime.MaxValue || Sett.Schd.Mode == ScheduleMode.None ? "Нет" : Sett.Schd.NextSync.ToString("dd.MM.yy HH:mm:ss");
            if (LastSync != DateTime.MinValue) lblLastSync.BackColor = LastSyncIsGood ? SystemColors.Control : Color.Red;

            StringBuilder sb = new StringBuilder();
            sb.Append(Sett.Mode == ElementMode.Archive ? "Архив" : "Зеркало");
            if (Sett.Mode == ElementMode.Archive)
            {
                sb.Append(Sett.ArchMode == ArchiveMode.Diff ? " дифференциальный" : " инкрементный");
                sb.Append(string.Format(" (линий: {0} размером: {1})", Sett.LinesCount == 0 ? "не огр." : Sett.LinesCount.ToString(), Sett.LineSize == 0 ? "не огр." : Sett.LineSize.ToString()));
            }
            else
            {
                sb.Append(Sett.DualSync ? " двустороннее" : " одностороннее");
            }
            sb.AppendLine("\r\nОбновление: " + Sett.Schd.ToString());
            sb.Append("Размер архива (в разработке)");

            lblInfo.Text = sb.ToString();
        }



        private void btnCancelSync_Click(object sender, EventArgs e)
        {
            sync.State = DirSync.ExecuteState.CancelSync;
            btnCancelSync.Enabled = false;
        }

        bool SyncNow = false;

        private void btnSyncNow_Click(object sender, EventArgs e)
        {
            SyncNow = true;
        }

        private void chbxDualSync_CheckedChanged(object sender, EventArgs e)
        {
            if (ChangeData != null) ChangeData(this, null);
        }

        private void tBtnRecover_Click(object sender, EventArgs e)
        {
            FormRecover frm = new FormRecover();
            frm.ShowDialog(Sett.DestDir);
            frm.Dispose();
        }

    }
}
